﻿using System;
using System.Management;
using System.Security.Cryptography;
using System.Text;

namespace LisenceUtils
{
    public interface IHardwareInfoHandle
    {
        string GenerateUid(string appName);
        byte[] GetUidInBytes(string uid);
        bool ValidateUidFormat(string uid);
    }

    public class HardwareInfoHandle : IHardwareInfoHandle
    {
        private const string CharList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private static readonly char[] CharArray = CharList.ToCharArray();

        /// <summary>
        ///     Get volume serial number of drive C
        /// </summary>
        /// <returns></returns>
        private HardwareInfoHandle()
        {
        }

        public static HardwareInfoHandle Default { get; } = new HardwareInfoHandle();

        /// <summary>
        ///     Combine CPU ID, Disk C Volume Serial Number and Motherboard Serial Number as device Id
        /// </summary>
        /// <returns></returns>
        public string GenerateUid(string appName)
        {
            //Combine the IDs and get bytes
            var id = string.Concat(appName, GetProcessorId(), GetMotherboardId(), GetDiskVolumeSerialNumber());
            var byteIds = Encoding.UTF8.GetBytes(id);

            //Use MD5 to get the fixed length checksum of the ID string
            var md5 = new MD5CryptoServiceProvider();
            var checksum = md5.ComputeHash(byteIds);

            //Convert checksum into 4 ulong parts and use ConvertBase36 to encode both
            var part1Id = Encode(BitConverter.ToUInt32(checksum, 0));
            var part2Id = Encode(BitConverter.ToUInt32(checksum, 4));
            var part3Id = Encode(BitConverter.ToUInt32(checksum, 8));
            var part4Id = Encode(BitConverter.ToUInt32(checksum, 12));

            //Concat these 4 part into one string
            return $"{part1Id}-{part2Id}-{part3Id}-{part4Id}";
        }

        public byte[] GetUidInBytes(string uid)
        {
            //Split 4 part Id into 4 ulong
            var ids = uid.Split('-');

            if (ids.Length != 4) throw new ArgumentException("Wrong uid");

            //Combine 4 part Id into one byte array
            var value = new byte[16];
            Buffer.BlockCopy(BitConverter.GetBytes(Decode(ids[0])), 0, value, 0, 8);
            Buffer.BlockCopy(BitConverter.GetBytes(Decode(ids[1])), 0, value, 8, 8);
            Buffer.BlockCopy(BitConverter.GetBytes(Decode(ids[2])), 0, value, 16, 8);
            Buffer.BlockCopy(BitConverter.GetBytes(Decode(ids[3])), 0, value, 24, 8);

            return value;
        }


        public bool ValidateUidFormat(string uid)
        {
            if (!string.IsNullOrWhiteSpace(uid))
            {
                var ids = uid.Split('-');

                return ids.Length == 4;
            }
            return false;
        }

        public string ShowUid(string appName)
        {
            return GenerateUid(appName);
        }

        public static IHardwareInfoHandle Create()
        {
            return Default;
        }

        private static string GetDiskVolumeSerialNumber()
        {
            try
            {
                var disk = new ManagementObject(@"Win32_LogicalDisk.deviceid=""c:""");
                disk.Get();
                return disk["VolumeSerialNumber"].ToString();
            }
            catch
            {
                return string.Empty;
            }
        }

        /// <summary>
        ///     Get CPU ID
        /// </summary>
        /// <returns></returns>
        private static string GetProcessorId()
        {
            try
            {
                var mbs = new ManagementObjectSearcher("Select ProcessorId From Win32_processor");
                var mbsList = mbs.Get();
                var id = string.Empty;
                foreach (var mo in mbsList)
                {
                    id = mo["ProcessorId"].ToString();
                    break;
                }

                return id;
            }
            catch
            {
                return string.Empty;
            }
        }

        /// <summary>
        ///     Get motherboard serial number
        /// </summary>
        /// <returns></returns>
        private string GetMotherboardId()
        {
            try
            {
                var mbs = new ManagementObjectSearcher("Select SerialNumber From Win32_BaseBoard");
                var mbsList = mbs.Get();
                var id = string.Empty;
                foreach (var mo in mbsList)
                {
                    id = mo["SerialNumber"].ToString();
                    break;
                }

                return id;
            }
            catch
            {
                return string.Empty;
            }
        }

        public static long Decode(string input)
        {
            long result = 0;
            double pow = 0;
            for (var i = input.Length - 1; i >= 0; i--)
            {
                var c = input[i];
                var pos = CharList.IndexOf(c);
                if (pos > -1)
                    result += pos*(long) Math.Pow(CharList.Length, pow);
                else
                    return -1;
                pow++;
            }
            return result;
        }

        public static string Encode(ulong input)
        {
            var sb = new StringBuilder();
            do
            {
                sb.Append(CharArray[input%(ulong) CharList.Length]);
                input /= (ulong) CharList.Length;
            } while (input != 0);

            return Reverse(sb.ToString());
        }

        private static string Reverse(string s)
        {
            var charArray = s.ToCharArray();
            Array.Reverse(charArray);
            return new string(charArray);
        }
    }
}